﻿using codeparser.net.v2.Entities;
using codeparser.net.v2.Exceptions;
using codeparser.net.v2.Lexer;
using codeparser.net.v2.Validators;

using Microsoft.VisualStudio.TestTools.UnitTesting;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace codeparser.net.v2.Tests
{
    /// <summary>
    /// Contains the tests for the lexer.
    /// </summary>
    [TestClass]
    public class LexerTests
    {
        /// <summary>
        /// Contains the lexer.
        /// </summary>
        private Lexer.Lexer _lexer;

        /// <summary>
        /// Initializes a test.
        /// </summary>
        [TestInitialize()]
        public void InitializeTest()
        {
            // Create a tag configuration.
            TagConfiguration tagConfiguration = new TagConfiguration();
            tagConfiguration.Add(new Tag(
                new TagElement("b"), TagClosingStyle.Required));
            tagConfiguration.Add(new Tag(
                new TagElement("flagEU"), TagClosingStyle.None));
            tagConfiguration.Add(new Tag(
                new TagElement("url"), TagClosingStyle.Required,
                new TagAttribute("href", AttributeStyles.Required | AttributeStyles.Default, AttributeValueStyles.Required),
                new TagAttribute("target", AttributeStyles.Optional, AttributeValueStyles.Required),
                new TagAttribute("enabled", AttributeStyles.Optional, AttributeValueStyles.None)));
            tagConfiguration.Add(new Tag(
                new TagElement("code"), TagClosingStyle.Required, TagContentStyle.NoParse,
                new TagAttribute("language", AttributeStyles.Optional, AttributeValueStyles.Required)));

            // Create a lexer.
            this._lexer = new Lexer.Lexer(tagConfiguration, TokenValidatorStrategy.CaseSensitive);
        }

        /// <summary>
        /// Tests null input.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(ArgumentNullException))]
        public void TestNull()
        {
            string input = null;

            IList<Token> actual = this._lexer.Tokenize(input);
        }

        /// <summary>
        /// Tests empty input.
        /// </summary>
        [TestMethod]
        public void TestEmpty()
        {
            string input = "";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(0, actual.Count);
        }

        /// <summary>
        /// Tests normal text.
        /// </summary>
        [TestMethod]
        public void TestText()
        {
            string input = "Hallo Welt!";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(1, actual.Count);
            Assert.AreEqual(TokenType.Text, actual[0].Type);
            Assert.AreEqual(input, actual[0].Value);
        }

        /// <summary>
        /// Tests a single tag as input.
        /// </summary>
        [TestMethod]
        public void TestSingleTag()
        {
            string input = "[flagEU]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(1, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual("flagEU", actual[0].Value);
        }

        /// <summary>
        /// Tests text within tags.
        /// </summary>
        [TestMethod]
        public void TestTextWithinTags()
        {
            string input = "[b]Hallo Welt![/b]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(3, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Text, actual[1].Type);
            Assert.AreEqual(TokenType.Element, actual[2].Type);
            Assert.AreEqual("b", actual[0].Value);
            Assert.AreEqual("Hallo Welt!", actual[1].Value);
            Assert.AreEqual("/b", actual[2].Value);
        }

        /// <summary>
        /// Tests the left bracket special character.
        /// </summary>
        [TestMethod]
        public void TestLeftBracket()
        {
            string input = "Hallo [[ Welt!";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(1, actual.Count);
            Assert.AreEqual(TokenType.Text, actual[0].Type);
            Assert.AreEqual("Hallo [ Welt!", actual[0].Value);
        }

        /// <summary>
        /// Tests mixed input of tags and text.
        /// </summary>
        [TestMethod]
        public void TestTagsAndTextMixed()
        {
            string input = "Hallo [b]Welt[/b]!";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(5, actual.Count);
            Assert.AreEqual(TokenType.Text, actual[0].Type);
            Assert.AreEqual(TokenType.Element, actual[1].Type);
            Assert.AreEqual(TokenType.Text, actual[2].Type);
            Assert.AreEqual(TokenType.Element, actual[3].Type);
            Assert.AreEqual(TokenType.Text, actual[4].Type);
            Assert.AreEqual("Hallo ", actual[0].Value);
            Assert.AreEqual("Welt", actual[2].Value);
            Assert.AreEqual("!", actual[4].Value);
        }

        /// <summary>
        /// Tests mixed input of tags and text in case insensitive mode.
        /// </summary>
        [TestMethod]
        public void TestTagsAndTextMixedInCaseInsensitiveMode()
        {
            string input = "Hallo [uRL HRef=http://www.goloroden.de]Welt[/Url]!";

            this._lexer.TokenValidatorStrategy =
                new TokenValidatorCaseInsensitiveStrategy();
            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(7, actual.Count);
            Assert.AreEqual(TokenType.Text, actual[0].Type);
            Assert.AreEqual(TokenType.Element, actual[1].Type);
            Assert.AreEqual(TokenType.Attribute, actual[2].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[3].Type);
            Assert.AreEqual(TokenType.Text, actual[4].Type);
            Assert.AreEqual(TokenType.Element, actual[5].Type);
            Assert.AreEqual(TokenType.Text, actual[6].Type);
            Assert.AreEqual("Hallo ", actual[0].Value);
            Assert.AreEqual("Welt", actual[4].Value);
            Assert.AreEqual("!", actual[6].Value);
        }

        /// <summary>
        /// Tests a left bracket within an element.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(InvalidCharacterException))]
        public void TestLeftBracketWithinElement()
        {
            string input = "[b[]";

            IList<Token> actual = this._lexer.Tokenize(input);
        }

        /// <summary>
        /// Tests the default attribute.
        /// </summary>
        [TestMethod]
        public void TestDefaultAttribute()
        {
            string input = "[url=http://www.example.com]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(2, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[1].Type);
            Assert.AreEqual("url", actual[0].Value);
            Assert.AreEqual("http://www.example.com", actual[1].Value);
        }

        /// <summary>
        /// Tests an attribute.
        /// </summary>
        [TestMethod]
        public void TestAttribute()
        {
            string input = "[url href=http://www.example.com]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(3, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Attribute, actual[1].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[2].Type);
            Assert.AreEqual("url", actual[0].Value);
            Assert.AreEqual("href", actual[1].Value);
            Assert.AreEqual("http://www.example.com", actual[2].Value);
        }

        /// <summary>
        /// Tests an attribute without value.
        /// </summary>
        [TestMethod]
        public void TestAttributeWithoutValue()
        {
            string input = "[url enabled]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(2, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Attribute, actual[1].Type);
            Assert.AreEqual("url", actual[0].Value);
            Assert.AreEqual("enabled", actual[1].Value);
        }

        /// <summary>
        /// Tests a quoted attribute.
        /// </summary>
        [TestMethod]
        public void TestQuotedAttribute()
        {
            string input = "[url href=\"http://www.example.com\"]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(3, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Attribute, actual[1].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[2].Type);
            Assert.AreEqual("url", actual[0].Value);
            Assert.AreEqual("href", actual[1].Value);
            Assert.AreEqual("http://www.example.com", actual[2].Value);
        }

        /// <summary>
        /// Tests a list of attributes.
        /// </summary>
        [TestMethod]
        public void TestMixedAttributes()
        {
            string input = "[url=\"http://www.example.com\" enabled target=_top]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(5, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[1].Type);
            Assert.AreEqual(TokenType.Attribute, actual[2].Type);
            Assert.AreEqual(TokenType.Attribute, actual[3].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[4].Type);
            Assert.AreEqual("url", actual[0].Value);
            Assert.AreEqual("http://www.example.com", actual[1].Value);
            Assert.AreEqual("enabled", actual[2].Value);
            Assert.AreEqual("target", actual[3].Value);
            Assert.AreEqual("_top", actual[4].Value);
        }

        /// <summary>
        /// Tests a quoted attribute.
        /// </summary>
        [TestMethod]
        public void TestMultipleSpacesBetweenAttributes()
        {
            string input = "[url   href=\"http://www.example.com\"]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(3, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Attribute, actual[1].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[2].Type);
            Assert.AreEqual("url", actual[0].Value);
            Assert.AreEqual("href", actual[1].Value);
            Assert.AreEqual("http://www.example.com", actual[2].Value);
        }

        /// <summary>
        /// Tests an invalid element.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(InvalidTagException))]
        public void TestInvalidOpeningTag()
        {
            string input = "[i]Hallo Welt![/i]";

            IList<Token> actual = this._lexer.Tokenize(input);
        }

        /// <summary>
        /// Tests a tag with content option NoParse.
        /// </summary>
        [TestMethod]
        public void TestTagWithContentNoParse()
        {
            string input = "[code]Hallo [b]Welt![/code]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(3, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Text, actual[1].Type);
            Assert.AreEqual(TokenType.Element, actual[2].Type);
            Assert.AreEqual("code", actual[0].Value);
            Assert.AreEqual("Hallo [b]Welt!", actual[1].Value);
            Assert.AreEqual("/code", actual[2].Value);
        }

        /// <summary>
        /// Tests a tag with content option NoParse and an attribute.
        /// </summary>
        [TestMethod]
        public void TestTagWithContentNoParseAndAttribute()
        {
            string input = "[code language=\"C#\"]Hallo [b]Welt![/code]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(5, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Attribute, actual[1].Type);
            Assert.AreEqual(TokenType.AttributeValue, actual[2].Type);
            Assert.AreEqual(TokenType.Text, actual[3].Type);
            Assert.AreEqual(TokenType.Element, actual[4].Type);
            Assert.AreEqual("code", actual[0].Value);
            Assert.AreEqual("language", actual[1].Value);
            Assert.AreEqual("C#", actual[2].Value);
            Assert.AreEqual("Hallo [b]Welt!", actual[3].Value);
            Assert.AreEqual("/code", actual[4].Value);
        }

        /// <summary>
        /// Tests an empty tag with content option NoParse.
        /// </summary>
        [TestMethod]
        public void TestEmptyTagWithContentNoParse()
        {
            string input = "[code][/code]";

            IList<Token> actual = this._lexer.Tokenize(input);

            Assert.AreEqual(2, actual.Count);
            Assert.AreEqual(TokenType.Element, actual[0].Type);
            Assert.AreEqual(TokenType.Element, actual[1].Type);
            Assert.AreEqual("code", actual[0].Value);
            Assert.AreEqual("/code", actual[1].Value);
        }

        /// <summary>
        /// Tests an invalid closing element.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(InvalidTagException))]
        public void TestInvalidClosingTag()
        {
            string input = "[b]Hallo Welt![/i]";

            IList<Token> actual = this._lexer.Tokenize(input);
        }

        /// <summary>
        /// Tests an invalid attribute with value.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(InvalidAttributeException))]
        public void TestInvalidAttributeWithValue()
        {
            string input = "[url location=http://www.example.com]";

            IList<Token> actual = this._lexer.Tokenize(input);
        }

        /// <summary>
        /// Tests an invalid attribute.
        /// </summary>
        [TestMethod]
        [ExpectedException(typeof(InvalidAttributeException))]
        public void TestInvalidAttribute()
        {
            string input = "[url disabled]";

            IList<Token> actual = this._lexer.Tokenize(input);
        }
    }
}